package com.example.redislearning.service.impl;

import com.example.redislearning.service.LotteryService;
import com.example.redislearning.util.RedisKeyUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * 积分抽奖服务实现类
 * 基于Redis实现抽奖活动管理、用户参与、中奖记录等功能
 */
@Service
public class LotteryServiceImpl implements LotteryService {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;


    private static final int DEFAULT_TTL_DAYS = 7;
    private static final int DAILY_LIMIT = 10;
    private static final int PARTICIPATION_COST = 10; // 每次抽奖消耗积分

    /**
     * 创建抽奖活动
     *
     * @param activityId   活动ID
     * @param activityName 活动名称
     * @param description  活动描述
     * @param totalPrize   总奖品数量
     * @param dailyPrize   每日奖品数量
     * @param startTime    活动开始时间
     * @param endTime      活动结束时间
     * @param prizePool    奖品池配置，key为奖品名称，value为奖品数量
     * @return 是否创建成功的布尔值
     * @throws RuntimeException 当创建抽奖活动失败时抛出异常
     */
    @Override
    public boolean createLotteryActivity(String activityId, String activityName, String description,
                                         int totalPrize, int dailyPrize, LocalDateTime startTime,
                                         LocalDateTime endTime, Map<String, Integer> prizePool) {
        try {
            String activityKey = RedisKeyUtil.getLotteryActivityKey(activityId);

            Map<String, Object> activity = new HashMap<>();
            activity.put("id", activityId);
            activity.put("name", activityName);
            activity.put("description", description);
            activity.put("totalPrize", totalPrize);
            activity.put("dailyPrize", dailyPrize);
            activity.put("startTime", startTime.toString());
            activity.put("endTime", endTime.toString());
            activity.put("status", "ACTIVE");
            activity.put("createdAt", LocalDateTime.now().toString());

            // 存储活动基本信息
            redisTemplate.opsForHash().putAll(activityKey, activity);

            // 存储奖品池
            String prizeKey = RedisKeyUtil.getLotteryPrizePoolKey(activityId);
            redisTemplate.opsForHash().putAll(prizeKey, prizePool);

            // 初始化每日奖品剩余数量
            String dailyPrizeKey = RedisKeyUtil.getLotteryDailyPrizeKey(activityId);
            redisTemplate.opsForValue().set(dailyPrizeKey, dailyPrize, 1, TimeUnit.DAYS);

            // 初始化总奖品剩余数量
            String totalPrizeKey = RedisKeyUtil.getLotteryTotalPrizeKey(activityId);
            redisTemplate.opsForValue().set(totalPrizeKey, totalPrize);

            // 设置活动过期时间
            Long expireSeconds = java.time.Duration.between(LocalDateTime.now(), endTime).getSeconds();
            redisTemplate.expire(activityKey, expireSeconds, TimeUnit.SECONDS);
            redisTemplate.expire(prizeKey, expireSeconds, TimeUnit.SECONDS);
            redisTemplate.expire(totalPrizeKey, expireSeconds, TimeUnit.SECONDS);

            return true;
        } catch (Exception e) {
            throw new RuntimeException("创建抽奖活动失败", e);
        }
    }

    /**
     * 用户参与抽奖
     *
     * @param userId     用户ID
     * @param activityId 活动ID
     * @return 抽奖结果，包含success、win、prize、prizeName、message等字段
     * @throws RuntimeException 当参与抽奖失败时抛出异常
     */
    @Override
    public Map<String, Object> participateLottery(Integer userId, String activityId) {
        try {
            Map<String, Object> result = new HashMap<>();

            // 检查活动是否存在且有效
            if (!isActivityValid(activityId)) {
                result.put("success", false);
                result.put("message", "抽奖活动不存在或已结束");
                return result;
            }

            // 检查用户积分是否足够
            if (!hasEnoughPoints(userId, PARTICIPATION_COST)) {
                result.put("success", false);
                result.put("message", "积分不足，无法参与抽奖");
                return result;
            }

            // 检查每日抽奖次数限制
            if (!canParticipateToday(userId, activityId)) {
                result.put("success", false);
                result.put("message", "今日抽奖次数已达上限");
                return result;
            }

            // 检查用户是否已参与过
            if (hasUserParticipated(userId, activityId)) {
                result.put("success", false);
                result.put("message", "您已参与过本次抽奖活动");
                return result;
            }

            // 扣除用户积分
            deductUserPoints(userId, PARTICIPATION_COST);

            // 记录用户参与
            String participantsKey = RedisKeyUtil.getLotteryParticipantsKey(activityId);
            redisTemplate.opsForSet().add(participantsKey, userId);

            // 增加参与次数
            incrementDailyParticipation(userId, activityId);

            // 执行抽奖逻辑
            Map<String, Object> prizeResult = drawPrize(activityId, userId);

            if ((Boolean) prizeResult.get("win")) {
                // 记录中奖信息
                String winnersKey = RedisKeyUtil.getLotteryWinnersKey(activityId);
                Map<String, Object> winnerInfo = new HashMap<>();
                winnerInfo.put("userId", userId);
                winnerInfo.put("prize", prizeResult.get("prize"));
                winnerInfo.put("prizeName", prizeResult.get("prizeName"));
                winnerInfo.put("winTime", LocalDateTime.now().toString());

                redisTemplate.opsForList().rightPush(winnersKey, winnerInfo);

                // 减少奖品数量
                updatePrizeCount(activityId, (String) prizeResult.get("prize"));

                result.put("success", true);
                result.put("win", true);
                result.put("prize", prizeResult.get("prize"));
                result.put("prizeName", prizeResult.get("prizeName"));
                result.put("message", "恭喜您中奖了！");
            } else {
                result.put("success", true);
                result.put("win", false);
                result.put("message", "很遗憾，未中奖，再接再厉！");
            }

            return result;
        } catch (Exception e) {
            throw new RuntimeException("参与抽奖失败", e);
        }
    }

    /**
     * 获取活动详情
     *
     * @param activityId 活动ID
     * @return 活动详情，包含活动基本信息、奖品池、剩余奖品数量等，活动不存在时返回null
     * @throws RuntimeException 当获取活动详情失败时抛出异常
     */
    @Override
    public Map<String, Object> getLotteryActivity(String activityId) {
        try {
            String activityKey = RedisKeyUtil.getLotteryActivityKey(activityId);
            Map<Object, Object> activity = redisTemplate.opsForHash().entries(activityKey);

            if (activity.isEmpty()) {
                return null;
            }

            Map<String, Object> result = new HashMap<>();
            // 修复类型不匹配问题
            for (Map.Entry<Object, Object> entry : activity.entrySet()) {
                result.put(entry.getKey().toString(), entry.getValue());
            }

            // 获取奖品池信息
            String prizeKey = RedisKeyUtil.getLotteryPrizePoolKey(activityId);
            Map<Object, Object> prizePool = redisTemplate.opsForHash().entries(prizeKey);
            result.put("prizePool", prizePool);

            // 获取剩余奖品数量
            String totalPrizeKey = RedisKeyUtil.getLotteryTotalPrizeKey(activityId);
            String dailyPrizeKey = RedisKeyUtil.getLotteryDailyPrizeKey(activityId);

            result.put("totalPrizeLeft", redisTemplate.opsForValue().get(totalPrizeKey));
            result.put("dailyPrizeLeft", redisTemplate.opsForValue().get(dailyPrizeKey));

            return result;
        } catch (Exception e) {
            throw new RuntimeException("获取活动详情失败", e);
        }
    }

    /**
     * 获取中奖用户列表
     *
     * @param activityId 活动ID
     * @param page       页码（从1开始）
     * @param size       每页数量
     * @return 中奖用户列表，每个元素包含用户ID、奖品、奖品名称、中奖时间等信息
     * @throws RuntimeException 当获取中奖用户列表失败时抛出异常
     */
    @Override
    public List<Map<String, Object>> getWinners(String activityId, int page, int size) {
        try {
            String winnersKey = RedisKeyUtil.getLotteryWinnersKey(activityId);
            Integer start = (page - 1) * size;
            Integer end = start + size - 1;

            List<Object> winners = redisTemplate.opsForList().range(winnersKey, start, end);
            List<Map<String, Object>> result = new ArrayList<>();

            if (winners != null) {
                for (Object winner : winners) {
                    if (winner instanceof Map) {
                        result.add((Map<String, Object>) winner);
                    }
                }
            }

            return result;
        } catch (Exception e) {
            throw new RuntimeException("获取中奖用户列表失败", e);
        }
    }

    /**
     * 获取活动参与用户数量
     *
     * @param activityId 活动ID
     * @return 参与用户数量
     * @throws RuntimeException 当获取参与用户数量失败时抛出异常
     */
    @Override
    public Integer getParticipantCount(String activityId) {
        try {
            String participantsKey = RedisKeyUtil.getLotteryParticipantsKey(activityId);
            Long size = redisTemplate.opsForSet().size(participantsKey);
            return size != null ? size.intValue() : 0;
        } catch (Exception e) {
            throw new RuntimeException("获取参与用户数量失败", e);
        }
    }

    /**
     * 获取用户参与状态
     *
     * @param userId     用户ID
     * @param activityId 活动ID
     * @return 用户参与状态，包含是否已参与、今日参与次数、每日限制、每次参与消耗积分等信息
     * @throws RuntimeException 当获取用户参与状态失败时抛出异常
     */
    @Override
    public Map<String, Object> getUserParticipationStatus(Integer userId, String activityId) {
        try {
            Map<String, Object> status = new HashMap<>();

            // 检查是否已参与
            String participantsKey = RedisKeyUtil.getLotteryParticipantsKey(activityId);
            boolean hasParticipated = redisTemplate.opsForSet().isMember(participantsKey, userId);

            // 获取今日参与次数
            String dailyKey = RedisKeyUtil.getUserDailyLotteryKey(userId, activityId);
            Integer dailyCount = (Integer) redisTemplate.opsForValue().get(dailyKey);

            status.put("hasParticipated", hasParticipated);
            status.put("dailyCount", dailyCount != null ? dailyCount : 0);
            status.put("dailyLimit", DAILY_LIMIT);
            status.put("costPerParticipation", PARTICIPATION_COST);

            return status;
        } catch (Exception e) {
            throw new RuntimeException("获取用户参与状态失败", e);
        }
    }

    /**
     * 获取用户抽奖历史
     *
     * @param userId 用户ID
     * @param page   页码（从1开始）
     * @param size   每页数量
     * @return 用户抽奖历史列表，每个元素包含活动信息、抽奖结果、时间等
     * @throws RuntimeException 当获取用户抽奖历史失败时抛出异常
     */
    @Override
    public List<Map<String, Object>> getUserLotteryHistory(Integer userId, int page, int size) {
        try {
            String historyKey = RedisKeyUtil.getUserLotteryHistoryKey(userId);
            Integer start = (page - 1) * size;
            Integer end = start + size - 1;

            List<Object> history = redisTemplate.opsForList().range(historyKey, start, end);
            List<Map<String, Object>> result = new ArrayList<>();

            if (history != null) {
                for (Object record : history) {
                    if (record instanceof Map) {
                        result.add((Map<String, Object>) record);
                    }
                }
            }

            return result;
        } catch (Exception e) {
            throw new RuntimeException("获取用户抽奖历史失败", e);
        }
    }

    /**
     * 结束抽奖活动
     *
     * @param activityId 活动ID
     * @return 是否成功结束的布尔值
     * @throws RuntimeException 当结束抽奖活动失败时抛出异常
     */
    @Override
    public boolean endLotteryActivity(String activityId) {
        try {
            String activityKey = RedisKeyUtil.getLotteryActivityKey(activityId);
            redisTemplate.opsForHash().put(activityKey, "status", "ENDED");
            return true;
        } catch (Exception e) {
            throw new RuntimeException("结束抽奖活动失败", e);
        }
    }

    /**
     * 获取活动列表
     *
     * @param page 页码（从1开始）
     * @param size 每页数量
     * @return 活动列表，每个元素包含活动基本信息、状态、奖品配置等
     * @throws RuntimeException 当获取活动列表失败时抛出异常
     */
    @Override
    public List<Map<String, Object>> getLotteryActivities(int page, int size) {
        try {
            Set<String> keys = redisTemplate.keys("lottery:activity:*");
            List<Map<String, Object>> activities = new ArrayList<>();

            if (keys != null) {
                List<String> keyList = new ArrayList<>(keys);
                int start = (page - 1) * size;
                int end = Math.min(start + size, keyList.size());

                for (int i = start; i < end; i++) {
                    String key = keyList.get(i);
                    Map<Object, Object> activity = redisTemplate.opsForHash().entries(key);
                    Map<String, Object> activityInfo = new HashMap<>();
                    activity.forEach((k, v) -> activityInfo.put(k.toString(), v));
                    activities.add(activityInfo);
                }
            }

            return activities;
        } catch (Exception e) {
            throw new RuntimeException("获取活动列表失败", e);
        }
    }

    @Override
    public List<Map<String, Object>> getAllActivities(){
        try {
            Set<String> keys = redisTemplate.keys("lottery:activity:*");
            List<Map<String, Object>> activities = new ArrayList<>();

            if (keys != null) {
                List<String> keyList = new ArrayList<>(keys);

                for (String key : keyList) {
                    Map<Object, Object> activity = redisTemplate.opsForHash().entries(key);
                    Map<String, Object> activityInfo = new HashMap<>();
                    activity.forEach((k, v) -> activityInfo.put(k.toString(), v));
                    activities.add(activityInfo);
                }
            }

            return activities;
        } catch (Exception e) {
            throw new RuntimeException("获取所有活动列表失败", e);
        }
    }


    // 私有辅助方法

    private boolean isActivityValid(String activityId) {
        Map<String, Object> activity = getLotteryActivity(activityId);
        return activity != null && "ACTIVE".equals(activity.get("status"));
    }

    /**
     * 检查用户是否有足够的积分
     *
     * @param userId 用户ID
     * @param points 需要消耗的积分数量
     * @return 是否有足够积分的布尔值
     * @throws RuntimeException 当检查用户积分失败时抛出异常
     */
    private boolean hasEnoughPoints(Integer userId, int points) {
        try {
            String userKey = RedisKeyUtil.getUserPointsKey(userId);
            String pointsStr = (String) redisTemplate.opsForHash().get(userKey, "points");
            int currentPoints = pointsStr != null ? Integer.parseInt(pointsStr) : 0;
            return currentPoints >= points;
        } catch (Exception e) {
            throw new RuntimeException("检查用户积分失败", e);
        }
    }

    private boolean canParticipateToday(Integer userId, String activityId) {
        String dailyKey = RedisKeyUtil.getUserDailyLotteryKey(userId, activityId);
        Integer count = (Integer) redisTemplate.opsForValue().get(dailyKey);
        return count == null || count < DAILY_LIMIT;
    }

    private boolean hasUserParticipated(Integer userId, String activityId) {
        String participantsKey = RedisKeyUtil.getLotteryParticipantsKey(activityId);
        return redisTemplate.opsForSet().isMember(participantsKey, userId);
    }

    /**
     * 扣除用户积分
     *
     * @param userId 用户ID
     * @param points 需要扣除的积分数量
     * @return 是否成功扣除积分的布尔值
     * @throws RuntimeException 当扣除用户积分失败时抛出异常
     */
    private boolean deductUserPoints(Integer userId, int points) {
        try {
            String userKey = RedisKeyUtil.getUserPointsKey(userId);
            redisTemplate.opsForHash().increment(userKey, "points", -points);
            return true;
        } catch (Exception e) {
            throw new RuntimeException("扣除用户积分失败", e);
        }
    }

    private void incrementDailyParticipation(Integer userId, String activityId) {
        String dailyKey = RedisKeyUtil.getUserDailyLotteryKey(userId, activityId);
        redisTemplate.opsForValue().increment(dailyKey);
        redisTemplate.expire(dailyKey, 1, TimeUnit.DAYS);
    }

    private Map<String, Object> drawPrize(String activityId, Integer userId) {
        Map<String, Object> result = new HashMap<>();

        // 获取奖品池
        String prizeKey = RedisKeyUtil.getLotteryPrizePoolKey(activityId);
        Map<Object, Object> prizePool = redisTemplate.opsForHash().entries(prizeKey);

        // 检查是否有奖品剩余
        boolean hasPrizeLeft = prizePool.values().stream()
                .anyMatch(count -> (Integer) count > 0);

        if (!hasPrizeLeft) {
            result.put("win", false);
            return result;
        }

        // 简单概率算法
        Random random = new Random();
        int totalProbability = 0;

        for (Map.Entry<Object, Object> entry : prizePool.entrySet()) {
            String prize = (String) entry.getKey();
            int count = (Integer) entry.getValue();

            if (count > 0) {
                // 根据奖品数量调整概率
                int probability = count * 10;
                totalProbability += probability;

                if (random.nextInt(100) < probability) {
                    result.put("win", true);
                    result.put("prize", prize);
                    result.put("prizeName", getPrizeName(prize));
                    return result;
                }
            }
        }

        result.put("win", false);
        return result;
    }

    private void updatePrizeCount(String activityId, String prize) {
        String prizeKey = RedisKeyUtil.getLotteryPrizePoolKey(activityId);
        String totalPrizeKey = RedisKeyUtil.getLotteryTotalPrizeKey(activityId);
        String dailyPrizeKey = RedisKeyUtil.getLotteryDailyPrizeKey(activityId);

        redisTemplate.opsForHash().increment(prizeKey, prize, -1);
        redisTemplate.opsForValue().decrement(totalPrizeKey);
        redisTemplate.opsForValue().decrement(dailyPrizeKey);
    }

    /**
     * 记录用户抽奖历史
     *
     * @param userId     用户ID
     * @param activityId 活动ID
     * @param win        是否中奖
     * @param prizeName  奖品名称
     * @throws RuntimeException 当记录抽奖历史失败时抛出异常
     */
    private void recordLotteryHistory(Integer userId, String activityId, boolean win, String prizeName) {
        try {
            String historyKey = RedisKeyUtil.getUserLotteryHistoryKey(userId);
            Map<String, Object> record = new HashMap<>();
            record.put("activityId", activityId);
            record.put("win", win);
            record.put("prize", prizeName);
            record.put("timestamp", new Date());

            redisTemplate.opsForList().leftPush(historyKey, record);
            redisTemplate.expire(historyKey, 30, TimeUnit.DAYS);
        } catch (Exception e) {
            throw new RuntimeException("记录抽奖历史失败", e);
        }
    }

    /**
     * 获取奖品名称
     *
     * @param prize 奖品标识
     * @return 奖品显示名称
     */
    private String getPrizeName(String prize) {
        Map<String, String> prizeNames = new HashMap<>();
        prizeNames.put("一等奖", "一等奖");
        prizeNames.put("二等奖", "二等奖");
        prizeNames.put("三等奖", "三等奖");
        prizeNames.put("四等奖", "四等奖");
        prizeNames.put("五等奖", "五等奖");
        prizeNames.put("谢谢参与", "谢谢参与");

        return prizeNames.getOrDefault(prize, prize);
    }
}